iT邦幫忙

2025 iThome 鐵人賽

DAY 24
0

1) 引言:型別也能做邏輯判斷?

在程式裡,我們經常會依條件來決定邏輯分支:

ts
CopyEdit
if (isLoggedIn) { ... } else { ... }

而 TypeScript 也提供一種語法,讓「型別」本身可以做類似的判斷,

這就是 條件型別(Conditional Types)

條件型別讓我們可以根據泛型參數的「型別關係」來決定結果型別,

進而做到更動態、更智慧的型別推導。


2) 基本語法

條件型別語法:

ts
CopyEdit
T extends U ? X : Y

意思是:

  • 如果 T 可以賦值給 U,型別結果就是 X
  • 否則結果就是 Y

範例:

ts
CopyEdit
type IsString<T> = T extends string ? true : false;

type A = IsString<string>; // true
type B = IsString<number>; // false


3) 泛型結合條件型別

條件型別通常與泛型一起使用:

ts
CopyEdit
function printId<T>(id: T) {
  type IdType = T extends string ? "Text ID" : "Numeric ID";
  let typeName: IdType;
}

這樣可以讓型別在不同輸入時自動變化。


4) infer 關鍵字:從型別中推導

infer 讓我們在條件型別中「提取」一部分型別。

範例:取出陣列元素的型別

ts
CopyEdit
type ElementType<T> = T extends (infer U)[] ? U : never;

type A = ElementType<string[]>; // string
type B = ElementType<number[]>; // number
type C = ElementType<boolean>;  // never


5) 分配式條件型別(Distributive Conditional Types)

當條件型別的泛型參數是 Union 型別 時,它會自動分配到每個成員上:

ts
CopyEdit
type ToArray<T> = T extends any ? T[] : never;

type A = ToArray<string | number>;
// 等同於 string[] | number[]


6) 實戰應用 1:判斷 API 回傳型別

假設我們有這樣的 API 回應型別:

ts
CopyEdit
type ApiResponse<T> =
  | { status: "success"; data: T }
  | { status: "error"; error: string };

我們可以用條件型別提取成功資料:

ts
CopyEdit
type ExtractData<T> = T extends { status: "success"; data: infer D } ? D : never;

type UserResponse = ApiResponse<{ id: string; name: string }>;
type UserData = ExtractData<UserResponse>;
// { id: string; name: string }


7) 實戰應用 2:找出物件中可為 null 的 key

ts
CopyEdit
type NullableKeys<T> = {
  [K in keyof T]: null extends T[K] ? K : never;
}[keyof T];

type User = {
  id: string;
  name: string | null;
  email: string;
};

type Result = NullableKeys<User>; // "name"


8) 實戰應用 3:API 工具結合條件型別

我們可以用條件型別讓 API 工具的回傳型別自動推導:

ts
CopyEdit
type Success<T> = { status: "success"; data: T };
type Fail = { status: "error"; error: string };

type ApiResult<T> = Success<T> | Fail;

async function callApi<T>(url: string): Promise<ApiResult<T>> {
  const res = await fetch(url);
  if (!res.ok) return { status: "error", error: "Failed" };
  return { status: "success", data: await res.json() };
}

type ExtractSuccess<T> = T extends { status: "success"; data: infer D } ? D : never;

// 使用時自動推導
const userResult = await callApi<User>("/api/user/1");
type UserData = ExtractSuccess<typeof userResult>;


9) 常見錯誤與陷阱

錯誤 1:忘記 extends 比較的是「可賦值性」

  • string extends string | number → true
  • 不是嚴格相等比較

錯誤 2:過度巢狀的條件型別

  • 會讓型別難以閱讀
  • 建議分拆成多個型別 alias 再組合

錯誤 3:忽略分配式行為

  • 有時候 union 型別不需要自動分配,要用 [...] 包住泛型來避免

10) 心法

  1. 條件型別不是 runtime if/else
    • 它只在編譯期決定型別
  2. 善用 infer
    • 從型別中抽取想要的部分非常強大
  3. 搭配工具型別與泛型使用
    • 可以大幅提升型別系統的表達力

上一篇
Day 23|泛型進階:打造可重用且型別安全的工具函式
系列文
我與型別的 30 天約定:TypeScript 入坑實錄24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言